Repository navigation
Add i18n support for homepage (国际化官网首页) - #31
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adds internationalization (i18n) support to the ObjectStack homepage, enabling both English and Chinese language versions. The implementation leverages the existing Next.js i18n routing infrastructure while keeping translations maintainable through a centralized module.
Changes:
- Created a new i18n module with TypeScript-typed translations for English and Chinese
- Converted the homepage component to async to access route parameters for language detection
- Replaced all hardcoded English strings with dynamic translation variables
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| apps/docs/lib/homepage-i18n.ts | New translation module defining the HomepageTranslations interface and providing English/Chinese translation objects with a language selector function |
| apps/docs/app/[lang]/page.tsx | Updated homepage to extract language from route params and use translations instead of hardcoded strings |
| packages/spec/src/system/manifest.test.ts | Removed unused MenuItem test cases and import |
| packages/spec/src/data/field.test.ts | Fixed extraneous closing brace |
| expect(() => MenuItemSchema.parse({ path: '/test' })).toThrow(); | ||
| }); | ||
| }); | ||
| import { ManifestSchema, type ObjectStackManifest } from './manifest.zod'; |
There was a problem hiding this comment.
The removed MenuItemSchema tests provided validation coverage for the menu item structure. Ensure that menu item validation is adequately covered elsewhere in the test suite, or consider keeping these tests to maintain comprehensive schema validation.
Adds entries 26-40 covering the gaps that make the helpdesk template 'pretty but not daily-usable' from an end-user perspective: P0 additions: - #26 No inline message composer on detail pages - #27 No external-user portal mechanism - #28 Attachment/file-list field UI not E2E P1 additions: - #29 No 'changed since last visit' indicator - #30 Bulk operations UI unverified (escalates #17) - #31 Rich-text editor scoped to comments only - #32 No first-class canned response / macro - #33 No collaboration presence indicators - #34 No keyboard-shortcut API - #35 No conditional SLA timer (pause on waiting_customer) - #36 Formula fields can't reference foreign object fields P2 additions: - #37 No chart drill-down - #38 No period-over-period analytics primitive - #39 No inbound-channel abstraction (email-to-ticket etc.) - #40 i18n translation namespace validation weak Includes 'user-pain → platform-gap' mapping table tracing each end-user complaint to a specific issue number. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…n to the viewer's language (objectui#13470) (#13487) <!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D1XH3Stf1pdUctjJez54hAZ)_ - [x] Adds no product-specific widget, dialog, hook or string (see AGENTS.md › Product-neutral renderer) Fixes #13470 Clause-②: no Metadata-protocol: none ## What changed `ReportSchema` in `@objectstack/spec` types a report's `label` and `description` as `I18nLabel`, so an inline locale map such as `{ 'zh-CN', en }` is valid metadata, and the `/meta` read serves it unchanged. Four places on the report route drew it raw: - `ReportView`'s page heading (`previewReport.title || previewReport.label`) and the line under it (`previewReport.description`). React refused the object as a child (error #31) and the page was blank for every viewer, the card's repro. - The app bar's report crumb in `AppHeader`. It handed the served label to `reportLabel` as its fallback, and `reportLabel` returns an unmatched fallback untouched, so the map reached JSX there too. With `ReportView` fixed alone, the page stayed blank through this crumb (measured: the ablation below). - The drill drawer's title for a whole-report drill (empty group key) fell back to `String(reportData.label)`, which reads `[object Object]`. Each now resolves the map in the viewer's language (`useObjectTranslation().language`) with `metadataText`, the app-shell reader the app's report index (`useAppReportEntries`) and recent items already name a report with. It is the spec's `resolveI18nLabel`, which agrees limb for limb with `pickLocalized` (the resolver the objectui#13380 fix used) and spells a miss `undefined`, so the existing fallbacks (`'Report Viewer'`, the humanized slug, the report name) keep working through `??`. A plain-string label or description draws exactly as before. One changeset: `@object-ui/app-shell` patch. The other surfaces that print a report's label or description were checked and already resolve the map: the app's report index (`AppReportIndexView`) and the ⌘K palette's app-report rows both read `useAppReportEntries`, which calls `metadataText`. The palette's navigation rows print the navigation entry's own label, not the report's. ## Tests New pins, both mounting the real component under a real `I18nProvider`, each leg in `en` and `zh-CN`, with a plain-string report as the control: - `packages/app-shell/src/views/ReportView.inlineLocaleLabel-13470.test.tsx`: the card's repro report, with the map on both `label` and `description`. Legs `HEADING_MAP_RESOLVES`, `DESCRIPTION_MAP_RESOLVES`, `DRILL_TITLE_MAP_RESOLVES`, `PLAIN_STRING_CONTROL`. - `packages/app-shell/src/layout/__tests__/AppHeader.reportCrumbInlineLocale-13470.test.tsx`: the `AppHeader.dashboardReportCrumb-12273` harness with `useObjectLabel` real. Legs `LABEL_MAP_RESOLVES`, `PLAIN_STRING_CONTROL`. Readings, all at head `899902d`: - The two new files: Test Files 2 passed (2), Tests 12 passed (12). - Every test file that imports `ReportView`, `AppHeader`, `AppReportIndexView` or `appReportIndex` (39 files, found by `git grep` over test files), with `pnpm exec vitest run --maxWorkers=2`: Test Files 39 passed (39), Tests 323 passed (323). - `turbo run type-check --filter=@object-ui/app-shell --concurrency=2`: 30 successful, 30 total. The package's `type-check` chains `tsconfig.test.json`, so both new test files are type-checked. - eslint on the four touched files: 0 errors. The 44 warnings are on lines this PR does not change. - `node scripts/check-changeset-presence.mjs`: pass (1 changeset for 1 released package). `pnpm check:new-line-citations`: 0 new citations. `node scripts/check-changeset-no-major.mjs`: pass. Narrowed, declared: the whole `packages/app-shell/` suite at `--maxWorkers=2` printed nothing for 60 minutes on this shared container and was stopped, so it is NOT MEASURED locally; CI's sharded `pnpm test` runs it. The narrowed set is every test that imports a changed module. Its blind spot: a test that mounts these components through another import path without naming them. Ablation, one site at a time on the committed fix. Each leg put the pre-fix line back, ran the two new files, then restored the file with `git checkout HEAD` and confirmed `git diff HEAD` was empty: - heading reverted: 6 failed, 6 passed. Every map leg of the `ReportView` file fails on `Objects are not valid as a React child (found: object with keys {zh-CN, en})`; the control legs and the crumb file pass. - description reverted: 6 failed, 6 passed, the same error. - drill title reverted: 2 failed, 10 passed. Only `DRILL_TITLE_MAP_RESOLVES` fails, in both languages, and the drawer reads `[object Object]`. - crumb reverted: 2 failed, 10 passed. Only the crumb file's `LABEL_MAP_RESOLVES` legs fail, on the same React error #31. ## Acceptance notes - Same defect on the page route, measured, not fixed here (outside this card's file surface): `AppHeader`'s page branch hands `pageDef.label` to `pageLabel` as its fallback the same way, and `PageSchema.label` is `I18nLabel` too. A throwaway probe (not committed) mounted this PR's crumb harness at `/apps/srm/page/welcome` with that page's label set to `{ 'zh-CN', en }`: it fails on `Objects are not valid as a React child (found: object with keys {zh-CN, en})`, so a page with an inline-map label blanks its route. Handed to the seat to file. - `DatasetReportRenderer`'s `resolveText` (the heading of each block of a `joined` report) reads only a map's `default` entry, so a block label written as `{ 'zh-CN', en }` shows the block's name instead of either string. Read-only observation from the code, not measured; nothing filed. - `ReportView` still reads `previewReport.title` before `label`. `ReportSchema` declares no root `title`, so the arm reads a key no valid document carries (the dashboard view retired its twin under ADR-0049). Left as it was, now resolved like `label`; outside this card. - The report crumb still passes through the `reports.NAME.label` bundle lookup (`reportLabel`), while the page heading draws the served label as given. That split predates this PR and is unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01EsHaC5nYMcJgk2Nr6fbVdG --------- Co-authored-by: Claude <noreply@anthropic.com>
…escription to the viewer's language (objectui#13488) (#13501) <!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DRqbNi4iARYYtqZmwLSPhaR)_ - [x] Adds no product-specific widget, dialog, hook or string (see AGENTS.md › Product-neutral renderer) Fixes #13488 Clause-②: no Metadata-protocol: none ## What changed `PageSchema` in `@objectstack/spec` types a page's `label` and `description` as `I18nLabel`, so an inline locale map such as `{ 'zh-CN', en }` is valid metadata, and the `/meta` read serves it unchanged. Two packages drew it raw on a page route, and React refused the object as a child (error #31), so the route was blank for every viewer: - **`@object-ui/app-shell`, `AppHeader`'s page branch.** It handed the served label to `pageLabel` as its fallback, and `pageLabel` returns an unmatched fallback untouched, so the map became the phone bar's title. It now resolves through `metadataText` in the viewer's language (`useObjectTranslation().language`), the call the report branch has used since objectui#13470, and still passes through the `pages.NAME.label` bundle lookup. The humanized slug stays the fallback when no label resolves. - **`@object-ui/components`, `PageRenderer`.** The page's own implicit heading (`schema.title ?? schema.label`), the line under it (`schema.description`), and the description it hands a titled `page:header` through `PageDescriptionContext` were all drawn raw. Each now resolves through `pickLocalized` in the viewer's language, the resolver `page:header` already uses for its own title in the same package. With only the app bar fixed, an `app`, `home` or `utility` page with a map label stayed blank through this heading (measured, below), so the claim was widened to this file (claim amendment on #13488). - **Swept, same file, same call:** `AppHeader`'s dashboard branch drew a served dashboard label straight into the phone bar's title. `DashboardSchema.label` is `I18nLabel` too, so it is now `metadataText(…) ?? dashboardLabel(…)`. A plain-string label or description draws exactly as before: `metadataText` and `pickLocalized` both pass a string through, and an empty one still falls to the slug or hides the description. Studio's page previews (`SourcePageEditor`, `PagePreview`) render through the same `PageRenderer`, so they get the fix too. One changeset: `@object-ui/app-shell` and `@object-ui/components` patch. ## Tests New pins, each mounting the real component under a real `I18nProvider`, each leg in `en` and `zh-CN` reading different strings out of one map, with plain strings as the control: - `packages/app-shell/src/layout/__tests__/AppHeader.pageDashboardTitleInlineLocale-13488.test.tsx`: the `AppHeader.reportCrumbInlineLocale-13470` harness with `useObjectLabel` real. Legs `PAGE_LABEL_MAP_RESOLVES`, `DASHBOARD_LABEL_MAP_RESOLVES`, two `PLAIN_STRING_CONTROL`s. - `packages/components/src/__tests__/page-inline-locale-label-13488.test.tsx`: an `app` page through the real `SchemaRenderer` and `PageRenderer`. Legs `HEADING_MAP_RESOLVES`, `DESCRIPTION_MAP_RESOLVES`, `HEADER_DESCRIPTION_MAP_RESOLVES` (titled `page:header`), `PLAIN_STRING_CONTROL`. Readings, final head `2c82e03` unless stated: - The two new files: Test Files 2 passed (2), Tests 16 passed (16). - Every test file that names `AppHeader`, `PageView`, `PageRenderer`, `SourcePageEditor`, `PagePreview` or `pageKindNode`, plus every `packages/components` test that names `PageRenderer`, `page:header`, `pageType`, the page renderer module or an `app`/`home`/`utility` page node (212 files, found by `git grep -lE` over test files), with `pnpm exec vitest run --maxWorkers=2`: Test Files 212 passed (212), Tests 2161 passed (2161). Run at `bf37518`; the only later commit, `2c82e03`, changes the type annotation of one new test's fixture, and both new files were re-run at `2c82e03` above. - `turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/components --concurrency=2 --continue`: 31 successful, 31 total. Both packages' `type-check` chain `tsconfig.test.json`, so the new test files are type-checked. - eslint on the five touched files: 0 errors. Every warning is on a line this PR does not add; the one on a changed line in `page.tsx` is the `(schema as any).label` read the old line already had. - `node scripts/check-changeset-presence.mjs`: pass (4 source files of 2 released packages, 1 changeset). `node scripts/check-changeset-no-major.mjs`: pass. `pnpm check:new-line-citations`: 0 new citations. Narrowed, declared: the whole `packages/app-shell/` and `packages/components/` suites were not run locally; CI's sharded `pnpm test` runs them. The narrowed set's blind spot is a test that mounts these components through another import path without naming them. Probe before the fix, on main `85b6a48`, through the real `SchemaRenderer` and `PageRenderer` (a throwaway file, not committed): an `app` page with a map `label`, one with a map `description`, and one with a titled `page:header` and a map `description` all failed on `Objects are not valid as a React child (found: object with keys {zh-CN, en})`. Ablation, one site at a time on the committed fix, through `scripts/ablation-replace.mjs` (anchor must hit once, blob must change, restore proven by blob equal to `HEAD` and an empty `git diff HEAD`), running the two new files each time: - page branch reverted (`pageDef?.label || humanizeSlug(pageName)`): 2 failed, 14 passed. Only `PAGE_LABEL_MAP_RESOLVES` fails, in both languages, on React error #31. - dashboard branch reverted (`dashboardDef?.label || dashboardLabel(…)`): 2 failed, 14 passed. Only `DASHBOARD_LABEL_MAP_RESOLVES` fails, on the same error. - `PageRenderer` heading reverted (`schema.title ?? schema.label` raw): 2 failed, 14 passed. Only `HEADING_MAP_RESOLVES` fails, on the same error. - `PageRenderer` description reverted (raw `schema.description`): 4 failed, 12 passed. `DESCRIPTION_MAP_RESOLVES` and `HEADER_DESCRIPTION_MAP_RESOLVES` fail, on the same error. ## Acceptance notes - `InterfaceListPage` (an interface page's list surface) reads `typeof page.label === 'string' ? page.label : page.name` for its heading and its record drawer's title, and draws `page.description` only when it is a string. A page with a map label does not blank there, but it shows the page's machine name instead of its label, and a map description is not shown. Read from the code on `85b6a48`, not measured; outside this claim's file surface. Handed to the seat. - `AppHeader`'s view branch hands `viewDef?.label || viewDef?.title` to `viewTabLabel`, and a list view's `label` is `I18nLabel` in the spec as well. The object page's tab bar (`ObjectView`) hands the same label to the same function, so whether a map view label blanks a view route is a question about `ObjectView`, not this header alone. Read from the code, not measured; not changed here. - The page's phone-bar title still passes the resolved label through the `pages.NAME.label` bundle lookup, while the page's own heading draws the served label. That split predates this PR and is unchanged, as on the report route. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_012oUFcAgU5BgTKe6fa7RxD8 --------- Co-authored-by: Claude <noreply@anthropic.com>
The homepage was hardcoded in English. This adds Chinese translations while leveraging the existing middleware and routing infrastructure.
Changes
Created
apps/docs/lib/homepage-i18n.tsHomepageTranslationsdefining all translatable stringsgetHomepageTranslations(lang)selector with fallback to EnglishUpdated
apps/docs/app/[lang]/page.tsxlangfrom route paramsExample
Screenshots
English (/):

Chinese (/cn):

Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.